package rbac

import (
	"context"

	"github.com/ViaQ/logerr/v2/kverrors"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/util/retry"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

// CreateOrUpdateRole attempts first to get the given role. If the
// role does not exist, the role will be created. Otherwise,
// if the role exists and the provided comparison func detects any changes
// an update is attempted. Updates are retried with backoff (See retry.DefaultRetry).
// Returns on failure an non-nil error.
func CreateOrUpdateRole(ctx context.Context, c client.Client, r *rbacv1.Role) error {
	current := &rbacv1.Role{}
	key := client.ObjectKey{Name: r.Name, Namespace: r.Namespace}
	err := c.Get(ctx, key, current)
	if err != nil {
		if apierrors.IsNotFound(err) {
			err = c.Create(ctx, r)

			if err == nil {
				return nil
			}

			return kverrors.Wrap(err, "failed to create role",
				"name", r.Name,
				"namespace", r.Namespace,
			)
		}

		return kverrors.Wrap(err, "failed to get role",
			"name", r.Name,
			"namespace", r.Namespace,
		)
	}

	if !equality.Semantic.DeepEqual(current, r) {
		err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
			if err := c.Get(ctx, key, current); err != nil {
				return kverrors.Wrap(err, "failed to get role",
					"name", r.Name,
					"namespace", r.Namespace,
				)
			}

			current.Rules = r.Rules
			if err := c.Update(ctx, current); err != nil {
				return err
			}
			return nil
		})
		if err != nil {
			return kverrors.Wrap(err, "failed to update role",
				"name", r.Name,
				"namespace", r.Namespace,
			)
		}
		return nil
	}
	return nil
}
